from cterm import *

class Symbol:
  def __init__(self, nr=-1, used=False, finalized=False):
    self.nr = nr
    self.used = used

  def __str__(self):
    return "%d (used=%d)" % (self.nr, self.used)

class BreakLoop:
  pass

def trimSequence(sequence, availability, sub=None):
  if sub == None:
    sub = len(sequence)

  if sub < 2:
    return []

  for i in range(len(sequence)-sub+1):
    newsequence = checkSequence(sequence[i:i+sub], availability)
    if newsequence != False:
      return newsequence

  return trimSequence(sequence, availability, sub-1)

def checkSequence(sequence, availability):
  ret = checkSequenceOnDataFlow(sequence)  

  try:
    newsequence, s, n = ret
  except:
    return False

  if checkSequenceOnAvailability(newsequence, availability) == False:
    return False

  if checkSequenceOnMeta(newsequence, availability) == False:
    return False

  return newsequence

def mergeSymbols(s1, s2):
  for s in s2.keys():
    s1[s] = s2[s]

def checkSequenceOnDataFlow(sequence, symbols=None, nr=0):
  if symbols == None:
    symbols = dict()

  newseq = []

  for op in sequence:
    if op.data != "Stat":
      print "      Rejected because operation", op, "is no statement"
      return False

    stat = op
    op = op.children[0]

    if not isinstance(op, FunCall):
      print "      Rejected because statement", op, "is no function call"
      return False

    if op.id().name() == "_finalize":
      try:
        del symbols[op.params().children[0]]
      except:
        # Finalizing unknown variable
        if newseq == []:
          print "      Rejected because finalization cannot be first element"
          return False

      newseq.append(ATerm("Stat", [op], stat.annotations))
    elif op.id().name()[0:4] == "scOp":  
      newop = ATerm("Stat", [FunCall(op.id(), ATerm("[]", op.params().children[0:2]))], stat.annotations)

      for (t, p) in zip(op.params().children[0].children[0].children[0].data[3:-3], op.params().children[2:]):
        if t == 'i':
          if isinstance(p, FunCall):
            try:
              subsequence, subsymbols, nr = checkSequenceOnDataFlow([p], symbols, nr)
            except:
              # Subsequence doesn't check out
              print "      Rejected because of subsequence rejection"
              return False

            symbols = mergeSymbols(symbols, subsymbols)
            newop.params().children.append(subsequence)
          else:
            if p in symbols.keys():
              if symbols[p].used == True:
                # A stream may only be used once
                print "      Rejected because of multiple stream use of", p
                return False
              
              symbols[p].used=True

              # Find generator
              found = False
              try:
                for o in range(len(newseq)-1, -1, -1):
                  for a in range(len(newseq[o].children[0].params().children)-1, -1, -1):
                    if newseq[o].children[0].id().name()[0:4] == "scOp":
                      if newseq[o].children[0].params().children[0].children[0].children[0].data[3+a] == 'I':
                        if newseq[o].children[0].params().children[2+a] == p:
                          newseq[o].children[0].params().children[2+a] = IntConst(str(symbols[p].nr))
                          raise BreakLoop
              except BreakLoop:
                found = True
                pass

              if found == False:
                # Couldn't find generator. Very strange.
                print "      Rejected because generator of", p, "couldn't be found"
                return False

              newop.children[0].params().children.append(IntConst(str(symbols[p].nr)))
            else:
              # External input
              newop.children[0].params().children.append(p)

        elif t == 'I':
          # Output. Assume external; if it's internal, it will be changed by the
          # operation that uses it as input.
          
          # IMPLICIT FINALIZATION!
          symbols[p] = Symbol(nr)
          nr += 1
          newop.children[0].params().children.append(p)

        elif t == 'd' or t == 'f':
          if p in symbols:
            # Cannot pass scalars in a merged operation
            print "      Rejected because passing scalars isn't allowed in a merged operation"
            return False
          
          newop.children[0].params().children.append(p)
        elif t == 'D' or t == 'F':
          symbols[p] = Symbol(nr)
          newop.children[0].params().children.append(p)
        else:
          # Unknown type
          print "      Rejected because argument datatype is unknown"
          return False
          
      newseq.append(newop)
    else:
      # Unknown function. This could be anything, so be cautious and reject
      # the sequence
      print "      Rejected because the sequence contains an unknown function", op.id().name()
      return False

  # Look for unfinalized inputs
  for p in symbols.keys():
    if symbols[p].used:
      print "      Rejected because symbol", symbols[p], "is used but not finalized"
      return False

  return newseq, symbols, nr

def checkSequenceOnAvailability(sequence, availability):
  procs = []
  first = True
  for op in sequence:
    op = op.children[0]
    if op.id().name()[0:4] == "scOp":
      nr = op.params().children[1].value()
      xmlopprocs = availability[nr].processors
      opprocs = []

      for proc in xmlopprocs:
        opprocs.append(proc.type)

      if first == False and procs != opprocs:
        print "      Rejected because of availability"
        return False

      first = False
      procs = opprocs

  return True

def checkSequenceOnMeta(sequence, availability):
  meta = dict()

  for op in sequence:
    op = op.children[0]
    if op.id().name()[0:4] == "scOp":
      nr = op.params().children[1].value()
      skeleton = availability[nr].skeleton
      
      for proc in availability[nr].processors:
        name = proc.name + "/" + skeleton + ".meta"
        
        try:
          f = open(name)
        except:
          print "      Rejected because of no metaskeleton"
          return False
        
        xmeta = readall(f)
        f.close()
        
        if proc in meta and xmeta != meta[proc]:
          print "      Rejected because of metaskeleton"
          return False
        
        meta[proc] = xmeta
        
  return True
